import frappe
from frappe.model.document import Document
from frappe.utils import getdate, add_months, nowdate, get_first_day, get_last_day, date_diff
from erpnext.accounts.doctype.journal_entry.journal_entry import JournalEntry
from erpnext.accounts.utils import get_fiscal_year

class FixedExpenseTable(Document):
    """
    固定费用二维表主表类，管理全年固定费用配置及自动记账逻辑
    """

    def before_save(self):
        """保存前校验整体配置合法性"""
        self.validate_fiscal_year()
        self.validate_accounts()
        self.validate_month_entries()
        self.validate_auto_post_date()  # 新增：校验自动记账日期

    def on_submit(self):
        """提交时自动检查当月是否需要立即记账（如已到记账日）"""
        self.check_and_post_current_month()

    def validate_fiscal_year(self):
        """校验会计年度是否有效，且包含当前配置的月份范围"""
        if not self.year:
            frappe.throw("请选择会计年度")
        
        fiscal_year = frappe.get_doc("Fiscal Year", self.year)
        current_date = getdate()
        if not (fiscal_year.year_start_date <= current_date <= fiscal_year.year_end_date):
            frappe.throw(f"当前日期不在 {self.year} 的会计年度范围内（{fiscal_year.year_start_date} 至 {fiscal_year.year_end_date}）")

    def validate_accounts(self):
        """校验借贷方科目是否存在且类型匹配"""
        # 校验费用科目（借方）必须存在
        if not frappe.db.exists("Account", self.expense_item):
            frappe.throw(f"费用科目 {self.expense_item} 不存在")
        
        # 校验贷方科目必须存在
        if not frappe.db.exists("Account", self.credit_account):
            frappe.throw(f"贷方科目 {self.credit_account} 不存在")

    def before_insert(self):
        """新建文档时自动初始化1-12月的配置行"""
        # 清空现有行（防止重复）
        self.monthly_amounts = []
        
        # 为1-12月各创建一行默认配置
        for month in range(1, 13):
            row = self.append("monthly_amounts", {})
            row.month = month
            row.post_status = "Pending"  # 默认为待记账状态
            # 金额可留空，由用户后续填写，或设置默认值（如0）
            row.amount = 0  # 可选：设置默认金额

    def validate_month_entries(self):
        """校验1-12月配置及金额有效性，处理类型和格式问题"""
        valid_months = set(range(1, 13))
        existing_months = set()
        all_month_values = []  # 存储所有行的原始数据（用于调试）

        # 检查子表数据是否加载完整
        if not self.monthly_amounts:
            frappe.throw("月份配置表为空，请添加1-12月数据")
        if len(self.monthly_amounts) != 12:
            frappe.throw(f"月份配置行数不正确（应为12行，实际{len(self.monthly_amounts)}行）")

        for row in self.monthly_amounts:
            # 记录原始数据用于调试
            original_month = row.month
            original_amount = row.amount
            all_month_values.append(
                f"第{row.idx}行: 月份={original_month}（类型:{type(original_month)}）, "
                f"金额={original_amount}（类型:{type(original_amount)}）"
            )

            # 处理月份（沿用之前的修复逻辑）
            # ...（省略月份处理代码，保持不变）

            existing_months.add(month)

            # 关键修复：金额校验（处理类型和格式问题）
            if original_amount is None:
                frappe.throw(f"第{row.idx}行（{month}月）金额为空，请填写")

            try:
                # 修复1：处理字符串格式的金额（如"3600"、"3,600"）
                cleaned_amount = str(original_amount).strip()  # 去空格
                cleaned_amount = cleaned_amount.replace(',', '')  # 去除千分位逗号
                amount = float(cleaned_amount)  # 强制转为数值

                # 修复2：严格校验金额>0
                if amount <= 0:
                    frappe.throw(f"第{row.idx}行（{month}月）金额必须大于0（当前值：{amount}）")

            except ValueError:
                frappe.throw(
                    f"第{row.idx}行（{month}月）金额格式无效\n"
                    f"原始值：{original_amount}（类型：{type(original_amount)}）\n"
                    f"清洗后的值：{cleaned_amount}"
                )

        # 检查缺失月份
        missing = sorted(valid_months - existing_months)
        if missing:
            frappe.throw(
                f"缺少月份：{missing}\n"
                f"已识别月份：{sorted(existing_months)}\n"
                f"所有行数据：\n{chr(10).join(all_month_values)}"
            )

    def validate_auto_post_date(self):
        """校验自动记账日期是否在1-28之间"""
        if self.auto_post_date < 1 or self.auto_post_date > 28:
            frappe.throw("自动记账日期必须设置在1-28日之间（避免月末日期无效）")

    def get_year_for_month(self, month):
        """根据会计年度获取指定月份对应的实际年份（解决会计年度跨自然年问题）"""
        fiscal_year = frappe.get_doc("Fiscal Year", self.year)
        start_month = fiscal_year.year_start_date.month
        start_year = fiscal_year.year_start_date.year
        
        # 计算该月份属于会计年度内的哪个自然年
        if month >= start_month:
            return start_year
        else:
            return start_year + 1

    def check_and_post_current_month(self):
        """检查当前月份是否到记账日，若未记账则触发记账"""
        today = getdate()
        current_month = today.month

        # 匹配当前月份的子表记录
        month_row = next((row for row in self.monthly_amounts if row.month == current_month), None)
        if not month_row:
            return

        # 校验状态：仅"待记账"且到记账日才执行
        if month_row.post_status == "Pending" and today.day >= self.auto_post_date:
            # 获取当前月份在会计年度中的对应年份
            current_fiscal_year = self.get_year_for_month(current_month)
            self.post_monthly_journal(month_row, current_fiscal_year, current_month)

    def post_monthly_journal(self, month_row, year, month):
        """为指定月份生成Journal Entry并更新状态"""
        try:
            # 生成记账日期（当月记账日）
            try:
                posting_date = getdate(f"{year}-{month}-{self.auto_post_date}")
            except ValueError:
                frappe.throw(f"生成的记账日期 {year}-{month}-{self.auto_post_date} 无效，请检查配置")

            # 确保日期在会计年度内
            fiscal_year_doc = frappe.get_doc("Fiscal Year", self.year)
            if not (fiscal_year_doc.year_start_date <= posting_date <= fiscal_year_doc.year_end_date):
                frappe.throw(f"记账日期 {posting_date} 超出会计年度 {self.year} 的范围（{fiscal_year_doc.year_start_date} 至 {fiscal_year_doc.year_end_date}）")

            # 关键修复：使用frappe.new_doc创建JournalEntry实例（替代直接实例化）
            je = frappe.new_doc("Journal Entry")  # 正确的文档创建方式
            
            # 设置必要字段（公司、日期等）
            je.company = self.company
            je.posting_date = posting_date
            je.narration = f"自动记账：{self.expense_item}（{month}月）"
            
            # 分录行：借方（费用科目）、贷方（应付科目）
            je.append("accounts", {
                "account": self.expense_item,
                "debit_in_account_currency": month_row.amount,
                "credit_in_account_currency": 0,
            })
            je.append("accounts", {
                "account": self.credit_account,
                "debit_in_account_currency": 0,
                "credit_in_account_currency": month_row.amount,
                "party_type": "Supplier",  # 如需关联供应商，确保主表有supplier字段并传入
                # "party": self.supplier  # 可选：关联具体供应商
            })
            
            je.insert(ignore_permissions=True)
            je.submit()

            # 更新子表状态：已记账+关联凭证号
            month_row.post_status = "Posted"
            month_row.journal_entry = je.name
            self.save(ignore_permissions=True)
            frappe.log_error(f"固定费用自动记账成功：{je.name}", "Fixed Expense Auto Post Success")

        except Exception as e:
            # 记录失败状态及错误日志
            month_row.post_status = "Failed"
            self.save(ignore_permissions=True)
            frappe.log_error(f"固定费用自动记账失败：{str(e)}", "Fixed Expense Auto Post Error")
            raise e

    def batch_post_pending_months(self):
        """批量处理所有未记账且已过记账日的月份（手动触发用）"""
        today = getdate()

        for row in self.monthly_amounts:
            if row.post_status == "Pending":
                # 获取该月份在会计年度中的对应年份
                month_year = self.get_year_for_month(row.month)
                # 计算该月份的记账日
                try:
                    month_post_date = getdate(f"{month_year}-{row.month}-{self.auto_post_date}")
                except ValueError:
                    frappe.log_error(f"月份 {row.month} 记账日期无效：{month_year}-{row.month}-{self.auto_post_date}", "Invalid Post Date")
                    continue

                # 检查是否已过记账日
                if month_post_date <= today:
                    self.post_monthly_journal(row, month_year, row.month)

        frappe.msgprint("批量记账处理完成，请检查各月份状态")

    def post_current_month(self):
        """手动触发当前月份记账"""
        today = getdate()
        current_month = today.month  # 类型为int（例如8）
        
        # 修复：确保遍历所有行，且用int类型匹配
        month_row = None
        for row in self.monthly_amounts:
            # 关键修复：将row.month转为int（避免字符串/数字类型不匹配）
            if int(row.month) == current_month:
                month_row = row
                break  # 找到后退出循环
        
        if not month_row:
            # 打印调试信息（便于排查）
            existing_months = [int(row.month) for row in self.monthly_amounts]
            frappe.throw(
                f"未找到{current_month}月的配置\n"
                f"当前月份：{current_month}（类型：{type(current_month)}）\n"
                f"已配置月份：{existing_months}（类型：{type(existing_months[0]) if existing_months else '无'}）"
            )
        
        if month_row.post_status == "Posted":
            frappe.msgprint(f"{current_month}月已记账")
            return
        
        # 后续逻辑保持不变...
        current_fiscal_year = self.get_year_for_month(current_month)
        self.post_monthly_journal(month_row, current_fiscal_year, current_month)
        return month_row.journal_entry


# 关键修复：添加@frappe.whitelist()装饰器
@frappe.whitelist()
def post_current_month(name):
    """模块级方法，供外部调用（必须定义在类之外）"""
    doc = frappe.get_doc("Fixed Expense Table", name)
    return doc.post_current_month()

# 注册定时任务调用的方法（供hooks.py使用）
def trigger_auto_post_for_all():
    """遍历所有已提交的固定费用配置，触发自动记账"""
    all_configs = frappe.get_all(
        "Fixed Expense Table",
        filters={"docstatus": 1},  # 仅处理已提交的配置
        fields=["name"]
    )
    for config in all_configs:
        try:
            doc = frappe.get_doc("Fixed Expense Table", config.name)
            doc.check_and_post_current_month()
        except Exception as e:
            frappe.log_error(f"处理配置 {config.name} 时出错：{str(e)}", "Fixed Expense Batch Process Error")